package org.visionlibrary.image.model;

public class Histogram {
	protected double[][] bins = null;
	protected boolean normalized = false;

	public Histogram(int numbands, int numbins) {
		bins = new double[numbands][numbins];
	}

	public Histogram(int numbins) {
		this(1, numbins);
	}

	public Histogram(double[] bins, boolean normalized) {
		this.normalized = normalized;
		this.bins = new double[1][];
		this.bins[0] = java.util.Arrays.copyOf(bins, bins.length);
	}

	public Histogram(double[] bins) {
		this(bins, false);
	}

	public void setBin(int band, int bin, double value) {
		if (bin < 0 && bin > bins[0].length - 1)
			throw new IndexOutOfBoundsException("Bin index is out of bounds.");

		if (band < 0 && band > bins.length - 1)
			throw new IndexOutOfBoundsException(
					"Histogram index is out of bounds.");

		bins[band][bin] = value;
	}

	public void setBin(int bin, double value) {
		setBin(0, bin, value);
	}

	public void addToBin(int band, int bin, double value) {
		if (bin < 0 && bin > bins[0].length - 1)
			throw new IndexOutOfBoundsException("Bin index is out of bounds.");

		if (band < 0 && band > bins.length - 1)
			throw new IndexOutOfBoundsException(
					"Histogram index is out of bounds.");

		bins[band][bin] += value;
	}

	public void addToBin(int bin, double value) {
		addToBin(0, bin, value);
	}

	public double getBin(int histogramIndex, int binIndex) {
		if (binIndex < 0 && binIndex > bins[0].length - 1)
			throw new IndexOutOfBoundsException("Bin index is out of bounds.");

		if (histogramIndex < 0 && histogramIndex > bins.length - 1)
			throw new IndexOutOfBoundsException(
					"Histogram index is out of bounds.");

		return bins[histogramIndex][binIndex];
	}

	public double getBin(int bin) {
		return getBin(0, bin);
	}

	public double[] getBins(int band) {
		return bins[band];
	}

	public boolean isNormalized() {
		return normalized;
	}

	public int getNumBins() {
		return bins[0].length;
	}

	public int getNumBands() {
		return bins.length;
	}

	public double getNumSamples() {
		double numSampels = 0;
		for (int b = 0; b < this.getNumBands(); b++)
			numSampels += this.getNumSampels(b, 0, getNumBins());

		return numSampels;
	}

	public double getNumSampels(int bound, int start, int length) {
		double sum = 0;
		for (int i = start; i < start + length; i++)
			sum += bins[bound][i];

		return sum;
	}

	public double[] getBand(int bandIndex){
		if (bandIndex < 0 && bandIndex > bins.length - 1)
			throw new IndexOutOfBoundsException("Band index is out of bounds.");
		return bins[bandIndex];
	}
	
	public double[] getBand() {
		return getBand(0);
	}
	
	public Histogram getNormalized() {
		Histogram normalized = new Histogram(bins.length, bins[0].length);
		normalized.normalized = true;

		double sum = this.getNumSamples();

		for (int h = 0; h < bins.length; h++)
			for (int b = 0; b < bins[0].length; b++)
				normalized.setBin(h, b, this.getBin(h, b) / sum);

		return normalized;
	}

	public Histogram getCumulative() {
		Histogram cumulative = new Histogram(bins.length, bins[0].length);

		for (int h = 0; h < this.getNumBands(); h++) {
			cumulative.setBin(h, 0, this.getBin(h, 0));
			for (int b = 1; b < bins[0].length; b++) {
				cumulative.setBin(h, b, cumulative.getBin(h, b - 1)
						+ this.getBin(h, b));
			}
		}

		return cumulative;
	}

	public static boolean isSizeEqual(Histogram first, Histogram second) {
		return (first.getNumBins() == second.getNumBins())
				&& (first.getNumBands() == second.getNumBands());
	}

	public static Histogram copy(Histogram histogram) {
		if (null == histogram)
			throw new NullPointerException("Histogram is null.");

		Histogram cp = new Histogram(histogram.bins.length,
				histogram.bins[0].length);
		cp.normalized = histogram.normalized;

		for (int i = 0; i < histogram.bins.length; i++)
			cp.bins[i] = java.util.Arrays.copyOf(histogram.bins[i],
					histogram.bins[i].length);

		return cp;
	}

	public static double bhattacharyyaDistance(Histogram first, Histogram second) {
		if (null == first)
			throw new NullPointerException("First histogram is null.");

		if (null == second)
			throw new NullPointerException("Second histogram is null.");

		if (!Histogram.isSizeEqual(first, second))
			throw new IllegalArgumentException("Histogram sizes dont match.");

		double sum = 0;
		for (int h = 0; h < first.getNumBands(); h++)
			for (int b = 0; b < first.getNumBins(); b++) {
				sum += Math.sqrt(first.getBin(h, b) * second.getBin(h, b));
			}

		return -Math.log(sum);
	}

	// histogram musi byc znormalizowany
	public static double euclideanDistance(Histogram first, Histogram second) {
		if (null == first)
			throw new NullPointerException("First histogram is null.");

		if (null == second)
			throw new NullPointerException("Second histogram is null.");

		if (!Histogram.isSizeEqual(first, second))
			throw new IllegalArgumentException("Histogram sizes dont match.");

		double sum = 0;
		for (int h = 0; h < first.getNumBands(); h++)
			for (int b = 0; b < first.getNumBins(); b++) {
				sum += (first.getBin(h, b) - second.getBin(h, b))
						* (first.getBin(h, b) - second.getBin(h, b));
			}

		return 1.0 - Math.sqrt(sum / (2.0d * first.getNumBands()));
	}

	// histogram nie musi byc znormalizowany
	public static double intersection(Histogram first, Histogram second) {
		if (null == first)
			throw new NullPointerException("First histogram is null.");

		if (null == second)
			throw new NullPointerException("Second histogram is null.");

		if (!Histogram.isSizeEqual(first, second))
			throw new IllegalArgumentException("Histogram sizes dont match.");

		double sum = 0;
		for (int h = 0; h < first.getNumBands(); h++)
			for (int b = 0; b < first.getNumBins(); b++) {
				sum += Math.min(first.getBin(h, b), second.getBin(h, b));
			}

		double intersection = sum
				/ Math.min(first.getNumSamples(), second.getNumSamples());

		return intersection;
	}

	public static double chi2(Histogram first, Histogram second) {
		if (null == first)
			throw new NullPointerException("First histogram is null.");

		if (null == second)
			throw new NullPointerException("Second histogram is null.");

		if (!Histogram.isSizeEqual(first, second))
			throw new IllegalArgumentException("Histogram sizes dont match.");

		double sum = 0;
		for (int h = 0; h < first.getNumBands(); h++)
			for (int b = 0; b < first.getNumBins(); b++) {
				double t = 0;
				if (!(Double.compare(first.getBin(h, b), 0.0) == 0
						&& Double.compare(second.getBin(h, b), 0.0) == 0)) {
					t = (first.getBin(h, b) - second.getBin(h, b))
							* (first.getBin(h, b) - second.getBin(h, b));
					t = t / (first.getBin(h, b) + second.getBin(h, b));
				}
				sum += t;
			}

		return sum;
	}

	public static double cosine(Histogram first, Histogram second) {
		if (null == first)
			throw new NullPointerException("First histogram is null.");

		if (null == second)
			throw new NullPointerException("Second histogram is null.");

		if (!Histogram.isSizeEqual(first, second))
			throw new IllegalArgumentException("Histogram sizes dont match.");
		
		double sum = 0;
		for (int h = 0; h < first.getNumBands(); h++)
			for (int b = 0; b < first.getNumBins(); b++) {
				sum += Math.min(first.getBin(h, b), second.getBin(h, b));
			}

		double intersection = sum
				/ Math.min(first.getNumSamples(), second.getNumSamples());
		
		double result = (2 / Math.PI) * Math.acos(intersection);
		
		return 1.0 - result;
	}
	
	public static Histogram getShiftedHistogramRightByOne(Histogram histogram) {
		Histogram shifted = Histogram.copy(histogram);
		for (int h = 0; h < shifted.getNumBands(); h++) {
			int length = shifted.bins[h].length;
			double last = shifted.bins[h][length - 1];
			System
					.arraycopy(shifted.bins[h], 0, shifted.bins[h], 1,
							length - 1);
			shifted.bins[h][0] = last;
		}

		return shifted;
	}
}
